﻿package disjunction.core.geom
{
	import disjunction.extensions.math.MathUtils;
	
	import flash.geom.Matrix;
	import flash.geom.Point;
	
	public class Vector2
	{
		//INSTANCE MEMBERS
		public var x:Number;
		public var y:Number;
		
		//STATIC MEMBERS
		//public static var ZERO:Vector2 = new Vector2();
		//INSTANCE METHODS
		
		public function Vector2(x:Number = 0, y:Number = 0)
		{
			this.x = x;
			this.y = y;
		}
		
		public function clone():Vector2
		{
			return new Vector2(x, y);
		}
		
		public function toVector3():Vector3
		{
			return new Vector3(x, y, 0);
		}
		
		public function equals(v:Vector2):Boolean
		{
			return (x == v.x) && (y == v.y);
		}

		public function add(v:Vector2):Vector2
		{
			return new Vector2(x + v.x, y + v.y);
		}
		
		public function addLocal(v:Vector2):Vector2
		{
			x += v.x;
			y += v.y;
			
			return this;
		}
	
		public function sub(v:Vector2):Vector2
		{
			return new Vector2(x - v.x, y - v.y);
		}
		
		public function subLocal(v:Vector2):Vector2
		{
			x -= v.x;
			y -= v.y;
			
			return this;
		}
		
		public function mul(n:Number):Vector2
		{
			return new Vector2(x * n, y * n);
		}
		
		public function mulLocal(n:Number):Vector2
		{
			x *= n;
			y *= n;
			return this;
		}



		public function dot(v:Vector2):Number
		{
			return x * v.x + y * v.y;
		}
	
		/**
		* Returns positive if v2 is clockwise of this Vector2,
		* minus if anticlockwise (Y axis pointing down, X axis to right)
		*/
		public function sign(v:Vector2):Number
		{
			if( y * v.x > x * v.y ) return -1;
			else return 1;
		}
	
	
		/**
		* Returns a Vector2 perpendicular to this Vector2. Defaults to 90 degrees (clockwise).
		*/
		public function perp(cw:Boolean = true):Vector2
		{
			return cw ? new Vector2(-y, x) : new Vector2(y, -x);
		}
	
		public function get length():Number
		{
			return Math.sqrt( x * x + y * y );
		}
	

		public function distance(v:Vector2):Number
		{
			return Math.sqrt( (v.y - y) * (v.y - y) + (v.x - x) * (v.x - x) );
		}
		
		

		public function truncate (m:Number):Vector2
		{
			return new Vector2(x, y).truncateLocal(m);
		}

		public function truncateLocal (m:Number):Vector2
		{
			if ( length > m )
			{
				this.normaliseLocal();
				x *= m;
				y *= m;
			}
			return this;
		}
	
		/**
		* given a normalized Vector2 this method reflects the Vector2 it is operating upon. 
		* (like the path of a ball bouncing off a wall)
		*/
		public function reflect(v:Vector2):Vector2
		{
			var vr : Vector2 = new Vector2();
			vr.y = y + 2.0 * dot(v) * v.reverse().y;
			vr.x = x + 2.0 * dot(v) * v.reverse().x;
			return vr;
		}
		
		public function reverse():Vector2
		{
			return new Vector2(-x, -y);
		}
		
		public function reverseLocal():Vector2
		{
			x *= -1;
			y *= -1;
			return this;
		}
		
		
		/**
		 * Return a normalised version of this vector.
		 */
		public function normalise():Vector2
		{
			return new Vector2(x/length, y/length);
		}

		/**
		* Normalise this Vector2.
		*/
		public function normaliseLocal():Vector2
		{
			var l:Number = this.length;
			if (l != 0)
			{
				x /= l;
				y /= l;
			}
			return this;
		}
		

		public function zero():void
		{
			x = y = 0;
		}

		public function isZero():Boolean
		{
			if (x == 0 && y == 0)
				return true;
			else
				return false;
		}
		
		/**
		* Get the angle in degrees or radians between this Vector2
		* and another
		*/
		public function angle(v:Vector2, deg:Boolean = false):Number
		{
			var cosA:Number;
			
			// if ( this.isZero() && v.isZero() )
			//	return NaN;
			if (this.isZero() && !v.isZero())
				cosA = v.x;
			else if (!this.isZero() && v.isZero())
				cosA = this.x;
			else
				cosA = this.dot(v) / (this.length * v.length);
			
			var a:Number = Math.acos(cosA);
			//	if we want it in degree
			if (deg)
			{
				var degrees:Number = a / Math.PI * 180;
				return degrees;
			}
	
			return a;
		}
		
		
		public function lerp(from:Vector2, to:Vector2, t:Number):Vector2
		{
			//clamp t
			t = Math.max(0, t);
			t = Math.min(1, t);
			x = from.x + ((to.x - from.x) * t);
			y = from.y + ((to.y - from.y) * t);
			
			return this;
		}
		//allows us to apply the result of a lerp to the local Vector2 so no
		//new Vector2 needs to be created
		public function lerpLocal(from:Vector2, to:Vector2, t:Number):Vector2
		{
			//clamp t
			t = Math.max(0, t);
			t = Math.min(1, t);
			x = from.x + ((to.x - from.x) * t);
			y = from.y + ((to.y - from.y) * t);
			
			return this;
		}
		
		
		public function wrapLocal(minX:Number, maxX:Number, minY:Number, maxY:Number):Vector2
		{
			x = MathUtils.wrapValue(x, minX, maxX);
			y = MathUtils.wrapValue(y, minY, maxY);			
			
			return this;
		}
		

		//angle in radians
		public function rotateLocal(angle:Number):Vector2
		{
			var ca:Number = Math.cos(angle);
			var sa:Number = Math.sin(angle);
			//cache original values or y calc will come off wrong
			//as x will already have been changed.
			var ox:Number = x;
			var oy:Number = y;
			this.x = ox*ca - oy*sa;
			this.y = ox*sa + oy*ca;
			return this;
		}
		
		//angle in radians
		public function rotate(angle:Number):Vector2
		{
			var ca:Number = Math.cos(angle);
			var sa:Number = Math.sin(angle);
			var nv:Vector2 = new Vector2(x*ca - y*sa, x*sa + y*ca);
			return nv;
		}
		
		
		public function minSignedAngleToward(extent:Vector2):Number
		{
			return Math.atan2(extent.x - this.x, -(extent.y - this.y))
		};

		//STATIC METHODS
		
		public static function Normalise(v:Vector2):Vector2
		{
			var vn : Vector2 = new Vector2();
			var l : Number = v.length;
			if (l != 0)
			{
				vn.x = v.x / l;
				vn.y = v.y / l;
			}
			return vn;
		}

		public static function Distance(v1:Vector2, v2:Vector2):Number
		{
			return Math.sqrt( (v2.y - v1.y) * (v2.y - v1.y) + (v2.x - v1.x) * (v2.x - v1.x) );
		}

		public static function Magnitude(v:Vector2):Number
		{
			return Math.sqrt( v.x * v.x + v.y * v.y );
		}
		
		//TODO remove to an external class
		public static function PointToVector2(p:Point):Vector2
		{
			return new Vector2(p.x, p.y);
		}
		//TODO remove to an external class
		public static function Vector2ToPoint(v:Vector2):Point
		{
			return new Point(v.x, v.y);
		}
		
		/*
		public static function RotateAroundOrigin(v:Vector2, ang:Number):Vector2
		{
			//create a transformation matrix
			var mtx:Matrix = new Matrix();
	
			//rotate
			mtx.rotate(ang);
	
			//now transform the object's vertices
			var oldPt:Point = Vector2.Vector2ToPoint(v);
			var newPt:Point = mtx.transformPoint(oldPt);
			return Vector2.PointToVector2(newPt);
		}
		*/
		
		public static function Mul(v:Vector2, n:Number):Vector2
		{
			return new Vector2(v.x * n, v.y * n);
		}
		
		
		public static function PerpProduct(v1:Vector2, v2:Vector2):Number
		{
			return v1.x * v2.y - v1.y * v2.x;
		}
		
		public static function Lerp(from:Vector2, to:Vector2, t:Number):Vector2
		{
			//clamp t
			t = Math.max(0, t);
			t = Math.min(1, t);
			return new Vector2(from.x + ((to.x - from.x) * t), from.y + ((to.y - from.y) * t));
		}

		public static function get ZERO():Vector2
		{
			return new Vector2();
		}
		
		public static function get NORTH():Vector2
		{
			return new Vector2(0, -1);
		}
		
		public static function get SOUTH():Vector2
		{
			return new Vector2(0, 1);
		}
		
		public static function get EAST():Vector2
		{
			return new Vector2(0, 1);
		}
		
		public static function get WEST():Vector2
		{
			return new Vector2(0, -1);
		}
		
		
		public function toString():String
		{
			return "Vector2 ["+x+", "+y+"]";			
		}
	}
}